package fr.mywiki.model.ejbAdapter;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;

import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.FinderException;
import javax.ejb.ObjectNotFoundException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;

import fr.mywiki.business.account.AccountLite;
import fr.mywiki.business.entry.EntryLite;
import fr.mywiki.business.entry.meta.EntryModel;
import fr.mywiki.business.entry.meta.EntryModelManager;
import fr.mywiki.business.file.FileLite;
import fr.mywiki.business.structure.StructureLite;
import fr.mywiki.business.versioning.MultiversionLite;
import fr.mywiki.business.versioning.VersionLite;
import fr.mywiki.model.ejbAdapter.services.AddLinkService;
import fr.mywiki.model.ejbAdapter.services.AddLinkServiceHome;
import fr.mywiki.model.ejbAdapter.services.CreateService;
import fr.mywiki.model.ejbAdapter.services.CreateServiceHome;
import fr.mywiki.model.ejbAdapter.services.FindAllService;
import fr.mywiki.model.ejbAdapter.services.FindAllServiceHome;
import fr.mywiki.model.ejbAdapter.services.GetLinkService;
import fr.mywiki.model.ejbAdapter.services.GetLinkServiceHome;
import fr.mywiki.model.ejbAdapter.services.GetLiteService;
import fr.mywiki.model.ejbAdapter.services.GetLiteServiceHome;
import fr.mywiki.model.ejbAdapter.services.RemoveLinkService;
import fr.mywiki.model.ejbAdapter.services.RemoveLinkServiceHome;
import fr.mywiki.model.ejbAdapter.services.RemoveService;
import fr.mywiki.model.ejbAdapter.services.RemoveServiceHome;
import fr.mywiki.model.ejbAdapter.services.UpdateService;
import fr.mywiki.model.ejbAdapter.services.UpdateServiceHome;
import fr.mywiki.model.ejbAdapter.utils.PersistenceException;
import fr.mywiki.model.queries.DatabaseFileController;
import fr.mywiki.model.queries.DatabaseMultiversionController;

/**
 * <p>
 * This class centralizes all the session beans. Every access to those beans should be done via a
 * method of this class.
 * </p>
 * <p>
 * This class works as a singleton, that is to say two threads cannot access it simultaneously.
 * </p>
 * 
 * @ejb.bean view-type = "remote"
 * 
 * @author tlombard
 */
public class ServicesFacade {
	
	/** The jndi context. */
	protected Context ctx = null;
	
	/////////////////////////////////////////////////////////////////////
	//                 The service local homes                         //
	/////////////////////////////////////////////////////////////////////
	
	/** The AddLink service home. */
	private AddLinkServiceHome addLinkService = null;
	
	/** The Create service home. */
	private CreateServiceHome createService = null;
	
	/** The FindAll service home. */
	private FindAllServiceHome findAllService = null;
	
	/** The GetLink service home. */
	private GetLinkServiceHome getLinkService = null;
	
	/** The GetLite service home. */
	private GetLiteServiceHome getLiteService = null;
	
	/** The RemoveLink service home. */
	private RemoveLinkServiceHome removeLinkService = null;
	
	/** The Remove service home. */
	private RemoveServiceHome removeService = null;
	
	/** The Update service home. */
	private UpdateServiceHome updateService = null;
	
	
	/////////////////////////////////////////////////////////////////////
	//                 The singleton management                        //
	/////////////////////////////////////////////////////////////////////	
	
	/** The singleton object, works as a unique token. */
	private static ServicesFacade servicesFacade = null;

	/**
	 * Returns the unique instance allowed for this class.
	 * 
	 * @return a <code>ServicesFacade</code> object that can be easily used. 
	 */
	public static ServicesFacade getInstance() {
		if (servicesFacade == null) {
			servicesFacade = new ServicesFacade();
		}
		return servicesFacade;
	}
	
	/** Private constructor, initiates the needed fields. */
	private ServicesFacade() {
		try {
			ctx = new InitialContext();
			addLinkService = (AddLinkServiceHome) PortableRemoteObject.narrow(
					ctx.lookup(AddLinkServiceHome.JNDI_NAME), AddLinkServiceHome.class); 
			createService = (CreateServiceHome) PortableRemoteObject.narrow(
					ctx.lookup(CreateServiceHome.JNDI_NAME), CreateServiceHome.class);
			findAllService = (FindAllServiceHome) PortableRemoteObject.narrow(
					ctx.lookup(FindAllServiceHome.JNDI_NAME), FindAllServiceHome.class);
			getLinkService = (GetLinkServiceHome) PortableRemoteObject.narrow(
					ctx.lookup(GetLinkServiceHome.JNDI_NAME), GetLinkServiceHome.class);
			getLiteService = (GetLiteServiceHome) PortableRemoteObject.narrow(
					ctx.lookup(GetLiteServiceHome.JNDI_NAME), GetLiteServiceHome.class);
			removeLinkService = (RemoveLinkServiceHome) PortableRemoteObject.narrow(
					ctx.lookup(RemoveLinkServiceHome.JNDI_NAME), RemoveLinkServiceHome.class);
			removeService = (RemoveServiceHome) PortableRemoteObject.narrow(
					ctx.lookup(RemoveServiceHome.JNDI_NAME), RemoveServiceHome.class);
			updateService = (UpdateServiceHome) PortableRemoteObject.narrow(
					ctx.lookup(UpdateServiceHome.JNDI_NAME), UpdateServiceHome.class);
		} catch (NamingException e) {
			e.printStackTrace();
		}
	}
	
	/////////////////////////////////////////////////////////////////////
	//                 The accessible methods                          //
	/////////////////////////////////////////////////////////////////////

	/**
	 * Adds a link between two <code>Value</code> objects via the relation <tt>relName</tt>.
	 * 
	 * @param value a <code>Value</code> object, the <i>from</i> node of the relation
	 * @param l_added a <code>Value</code> object, the <i>to</i> node of the relation
	 * @param relName the considered relation.
	 * @throws RemoteException
	 */
	public void addLink(Value value, Value added, String relName)
			throws RemoteException, FinderException {
		String typeEnt = value.getTypeEnt();
		if (AccountLite.TYPE_ENT.equals(typeEnt))
			getAddLinkService().addLinkAccount(value.getId(), added.getId(), relName);
		else if (EntryLite.TYPE_ENT.equals(typeEnt))
			getAddLinkService().addLinkEntry(value.getId(), added.getId(), relName);
		else if (StructureLite.TYPE_ENT.equals(typeEnt))
			getAddLinkService().addLinkStructure(value.getId(), added.getId(), relName);
		else if (VersionLite.TYPE_ENT.equals(typeEnt))
			getAddLinkService().addLinkVersion(value.getId(), added.getId(), relName);
	}
	
	/**
	 * Creates a empty <code>Value</code> object of the correct type.
	 * 
	 * @param typeEnt a String representing the entity type of the object to be created.
	 * @return a <code>Value</code> object. 
	 */
	public Value create(String typeEnt) throws PersistenceException, RemoteException {
		if (AccountLite.TYPE_ENT.equals(typeEnt))
			return getCreateService().createAccount();
		if (EntryLite.TYPE_ENT.equals(typeEnt))
			return getCreateService().createEntry();
		if (EntryModel.TYPE_ENT.equals(typeEnt))
			return EntryModelManager.createModel();
		if (FileLite.TYPE_ENT.equals(typeEnt))
			return DatabaseFileController.createFile();
		if (MultiversionLite.TYPE_ENT.equals(typeEnt))
			return DatabaseMultiversionController.createMultiversion();
		if (StructureLite.TYPE_ENT.equals(typeEnt))
			return getCreateService().createStructure();
		if (VersionLite.TYPE_ENT.equals(typeEnt))
			return getCreateService().createVersion();
		return null;
	}
	
	/**
	 * Returns all the objects having the specified type.
	 * 
	 * @param typeEnt the type of objects which are needed.
	 * @return a <code>Collection</code> of <code>Value</code> objects.
	 */
	public Collection findAll(String typeEnt) throws RemoteException {
		if (AccountLite.TYPE_ENT.equals(typeEnt))
			return getFindAllService().findAllAccount();
		if (EntryLite.TYPE_ENT.equals(typeEnt))
			return getFindAllService().findAllEntry();
		if (EntryModel.TYPE_ENT.equals(typeEnt))
			return EntryModelManager.findAllModels();
		if (FileLite.TYPE_ENT.equals(typeEnt))
			return DatabaseFileController.findAllFile();
		if (FileLite.TYPE_ENT_LIGHT.equals(typeEnt))
			return DatabaseFileController.findAllFileLight();
		if (MultiversionLite.TYPE_ENT.equals(typeEnt))
			return DatabaseMultiversionController.findAllMultiversion();
		if (StructureLite.TYPE_ENT.equals(typeEnt))
			return getFindAllService().findAllStructure();
		if (VersionLite.TYPE_ENT.equals(typeEnt))
			return getFindAllService().findAllVersion();
		return null;
	}
	
	/**
	 * Returns all the <code>Value</code> objects linked to <tt>value</tt> by the relation <tt>relName</tt>.
	 * 
	 * @param value a <code>Value</code> object.
	 * @param relName a relation name.
	 * @return a <code>Collection</code> of <code>Value</code> objects.
	 */
	public Collection getLinks(Value value,String relName) throws RemoteException, FinderException {
		String typeEnt = value.getTypeEnt();
		if (AccountLite.TYPE_ENT.equals(typeEnt))
			return getGetLinkService().getLinksAccount(value.getId(), relName);
		if (EntryLite.TYPE_ENT.equals(typeEnt))
			return getGetLinkService().getLinksEntry(value.getId(),relName);
		if (FileLite.TYPE_ENT.equals(typeEnt))
			return new ArrayList();
		if (MultiversionLite.TYPE_ENT.equals(typeEnt))
			return new ArrayList();
		if (StructureLite.TYPE_ENT.equals(typeEnt))
			return getGetLinkService().getLinksStructure(value.getId(),relName);
		if (VersionLite.TYPE_ENT.equals(typeEnt))
			return getGetLinkService().getLinksVersion(value.getId(),relName);
		return null;
	}
	
	/**
	 * Returns the unique <code>Value</code> object of the correct type and having the specified
	 * identifier.
	 * 
	 * @param id a <code>Long</code> object representing the identifier of the desired object.
	 * @param typeEnt the entity type of the searched object.
	 * @return a <code>Value</code> object.
	 * @throws ObjectNotFoundException if the entry having the specified identifier could not be
	 *             found in the database.
	 */
	public Value getValue(Long id, String typeEnt) throws ObjectNotFoundException, RemoteException {
		if (AccountLite.TYPE_ENT.equals(typeEnt))
			return getGetLiteService().getAccountLite(id);
		if (EntryLite.TYPE_ENT.equals(typeEnt))
			return getGetLiteService().getEntryLite(id);
		if (EntryModel.TYPE_ENT.equals(typeEnt))
			return EntryModelManager.findModelById(id);
		if (FileLite.TYPE_ENT.equals(typeEnt))
			return DatabaseFileController.findFileByPrimaryKey(id);
		if (MultiversionLite.TYPE_ENT.equals(typeEnt))
			return DatabaseMultiversionController.findMultiversionByPrimaryKey(id);
		if (StructureLite.TYPE_ENT.equals(typeEnt))
			return getGetLiteService().getStructureLite(id);
		if (VersionLite.TYPE_ENT.equals(typeEnt))
			return getGetLiteService().getVersionLite(id);
		return null;
	}
	
	/**
	 * Removes a link between two <code>value</code> objects linked by <tt>relName</tt>
	 * relation.
	 * 
	 * @param value the <i>from</i> value of the link.
	 * @param removed the <i>to</i> value of the link.
	 * @param relName the relation name.
	 */
	public void removeLink(Value value, Value removed, String relName) throws RemoteException, FinderException {
		String typeEnt = value.getTypeEnt();
		if(AccountLite.TYPE_ENT.equals(typeEnt))
			getRemoveLinkService().removeLinkAccount(value.getId(),removed.getId(),relName);
		else if(EntryLite.TYPE_ENT.equals(typeEnt))
			getRemoveLinkService().removeLinkEntry(value.getId(),removed.getId(),relName);
		else if(StructureLite.TYPE_ENT.equals(typeEnt))
			getRemoveLinkService().removeLinkStructure(value.getId(),removed.getId(),relName);
		else if(VersionLite.TYPE_ENT.equals(typeEnt))
			getRemoveLinkService().removeLinkVersion(value.getId(),removed.getId(),relName);
	}
	
	/**
	 * Removes the specified <code>Value</code> object from the database.
	 * 
	 * @param value a <code>Value</code> object to be removed.
	 * @throws ObjectNotFoundException if the object was not found in the database.
	 */
	public void remove(Value value) throws ObjectNotFoundException, RemoteException {
		String typeEnt = value.getTypeEnt();
		if(AccountLite.TYPE_ENT.equals(typeEnt))
			getRemoveService().removeAccount(value.getId());
		else if(EntryLite.TYPE_ENT.equals(typeEnt))
			getRemoveService().removeEntry(value.getId());
		else if(EntryModel.TYPE_ENT.equals(typeEnt))
			EntryModelManager.removeModel((EntryModel)value);
		else if (FileLite.TYPE_ENT.equals(typeEnt))
			DatabaseFileController.removeFile(value.getId());
		else if (MultiversionLite.TYPE_ENT.equals(typeEnt))
			DatabaseMultiversionController.removeMultiversion(value.getId());
		else if(StructureLite.TYPE_ENT.equals(typeEnt))
			getRemoveService().removeStructure(value.getId());
		else if(VersionLite.TYPE_ENT.equals(typeEnt))
			getRemoveService().removeVersion(value.getId());
	}
	
	/**
	 * Updates the attributes/fields of the <code>Value</code> object given in parameter, i.e. the
	 * value in the database is changed and is guaranteed to gain the same values as the
	 * <code>Value</code> object.
	 * 
	 * @param value a <code>Value</code> object.
	 * @throws ObjectNotFoundException if the object was not found in the database.
	 */
	public void update(Value value) throws ObjectNotFoundException, RemoteException {
		String typeEnt = value.getTypeEnt();
		if(AccountLite.TYPE_ENT.equals(typeEnt))
			getUpdateService().updateAccount((AccountLite)value);
		else if(EntryLite.TYPE_ENT.equals(typeEnt))
			getUpdateService().updateEntry((EntryLite)value);
		else if(EntryModel.TYPE_ENT.equals(typeEnt))
			EntryModelManager.updateModel((EntryModel)value);
		else if(FileLite.TYPE_ENT.equals(typeEnt))
			DatabaseFileController.updateFile((FileLite) value);
		else if(MultiversionLite.TYPE_ENT.equals(typeEnt))
			DatabaseMultiversionController
					.updateMultiversion((MultiversionLite) value);
		else if(StructureLite.TYPE_ENT.equals(typeEnt))
			getUpdateService().updateStructure((StructureLite)value);
		else if(VersionLite.TYPE_ENT.equals(typeEnt))
			getUpdateService().updateVersion((VersionLite)value);
	}
	
	/////////////////////////////////////////////////////////////////////
	//                 Getters for the service homes                   //
	/////////////////////////////////////////////////////////////////////
	
	private AddLinkService getAddLinkService() {
		try {
			return addLinkService.create();
		} catch (CreateException e) {
			throw new EJBException(e);
		} catch (RemoteException e) {
			throw new EJBException(e);
		}
	}

	private CreateService getCreateService() {
		try {
			return createService.create();
		} catch (CreateException e) {
			throw new EJBException(e);
		} catch (RemoteException e) {
			throw new EJBException(e);
		}
	}

	private FindAllService getFindAllService() {
		try {
			return findAllService.create();
		} catch (CreateException e) {
			throw new EJBException(e);
		} catch (RemoteException e) {
			throw new EJBException(e);
		}
	}
	
	private GetLinkService getGetLinkService() {
		try {
			return getLinkService.create();
		} catch (CreateException e) {
			throw new EJBException(e);
		} catch (RemoteException e) {
			throw new EJBException(e);
		}
	}

	private GetLiteService getGetLiteService() {
		try {
			return getLiteService.create();
		} catch (CreateException e) {
			throw new EJBException(e);
		} catch (RemoteException e) {
			throw new EJBException(e);
		}
	}
	
	private RemoveLinkService getRemoveLinkService() {
		try {
			return removeLinkService.create();
		} catch (CreateException e) {
			throw new EJBException(e);
		} catch (RemoteException e) {
			throw new EJBException(e);
		}
	}
	
	private RemoveService getRemoveService() {
		try {
			return removeService.create();
		} catch (CreateException e) {
			throw new EJBException(e);
		} catch (RemoteException e) {
			throw new EJBException(e);
		}
	}
	
	private UpdateService getUpdateService() {
		try {
			return updateService.create();
		} catch (CreateException e) {
			throw new EJBException(e);
		} catch (RemoteException e) {
			throw new EJBException(e);
		}
	}
}
